package com.ccrfid.rmc6.service.vehicle;

import com.ccrfid.rmc6.appconfig.exception.pojo.AppException;
import com.ccrfid.rmc6.appconfig.exception.pojo.CustomErrors;
import com.ccrfid.rmc6.entity.vehicle.*;
import com.ccrfid.rmc6.lbservice3.pojo.msg.MsgLocate;
import com.ccrfid.rmc6.pojo.dto.LocatorAssetCount;
import com.ccrfid.rmc6.pojo.dto.vehicle.VehicleInfoDto;
import com.ccrfid.rmc6.pojo.dto.vehicle.VehicleMoveDto;
import com.ccrfid.rmc6.pojo.dto.vehicle.VehicleMoveInLocationDto;
import com.ccrfid.rmc6.repository.search.SearchCriteria;
import com.ccrfid.rmc6.repository.vehicle.*;
import com.ccrfid.rmc6.util.GeomUtil;
import com.vividsolutions.jts.geom.Polygon;
import org.hibernate.query.NativeQuery;
import org.hibernate.spatial.JTSGeometryType;
import org.hibernate.spatial.dialect.mysql.MySQLGeometryTypeDescriptor;
import org.hibernate.transform.Transformers;
import org.hibernate.type.IntegerType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
import javax.ws.rs.core.Response;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Service
@Transactional
public class VehicleServiceImpl implements VehicleService {
    private static final Logger logger = LoggerFactory.getLogger(VehicleServiceImpl.class);

    @PersistenceContext
    private EntityManager entityManager;
    @Autowired
    private VehicleRepository vehicleRepository;
    @Autowired
    private VehiclePhotoRepository vehiclePhotoRepository;
    @Autowired
    private VehicleEntryRecordRepository vehicleEntryRecordRepository;
    @Autowired
    private VehicleEntryRecordViewRepository vehicleEntryRecordViewRepository;
    @Autowired
    private VehicleMoveRepository vehicleMoveRepository;

    @Override
    public Page<VehicleEntity> getVehiclePage(SearchCriteria criteria, Pageable pageable) {
        return vehicleRepository.search(criteria, pageable);
    }

    private void assertVehicleIdNotExists(Long vehicleId) throws AppException {
        if (vehicleId == null) {
            throw new AppException(Response.Status.BAD_REQUEST.getStatusCode(),
                    CustomErrors.param_is_null.getCode(), CustomErrors.param_is_null.getReason(), "");
        }
        if (!vehicleRepository.existsById(vehicleId)) {
            throw new AppException(Response.Status.NOT_FOUND.getStatusCode(),
                    CustomErrors.vehicle_not_found.getCode(), CustomErrors.vehicle_not_found.getReason(), "");
        }
    }

    @Override
    public VehicleEntity getVehicleById(Long vehicleId) throws AppException {

        assertVehicleIdNotExists(vehicleId);

        return vehicleRepository.getById(vehicleId);
    }

    @Override
    public VehicleEntity getVehicleByCardNumber(Long cardNumber) {

        return vehicleRepository.getByCardNumber(cardNumber);
    }

    @Override
    public VehicleEntity createVehicle(VehicleEntity entity) throws AppException {
        if (entity.getEntryStatus() == null) {
            entity.setEntryStatus(0);
        }
        return vehicleRepository.save(entity);
    }

    @Override
    public VehicleEntity updateVehicle(Long vehicleId, VehicleEntity entity) throws AppException {

        assertVehicleIdNotExists(vehicleId);

        if (!vehicleId.equals(entity.getId())) {
            throw new AppException(Response.Status.BAD_REQUEST.getStatusCode(),
                    CustomErrors.different_args.getCode(), CustomErrors.different_args.getReason(), "");
        }

        VehicleEntity vehicleEntity = vehicleRepository.getById(vehicleId);
        if (vehicleEntity != null) {
            entity.setEntryStatus(vehicleEntity.getEntryStatus());
        }

        return vehicleRepository.save(entity);
    }

    @Override
    public Boolean deleteVehicle(Long vehicleId) throws AppException {

        assertVehicleIdNotExists(vehicleId);

        if (vehiclePhotoRepository.existsByVehicleId(vehicleId)) {
            vehiclePhotoRepository.deleteByVehicleId(vehicleId);
        }
        vehicleRepository.deleteById(vehicleId);

        return true;
    }

    @Override
    public byte[] getVehiclePhoto(Long vehicleId) throws AppException {
        if (vehiclePhotoRepository.existsByVehicleId(vehicleId)) {
            return vehiclePhotoRepository.getByVehicleId(vehicleId).getImage();
        } else {
            return new byte[0];
        }
    }

    @Override
    public Boolean updateVehiclePhoto(Long vehicleId, byte[] image) throws AppException {
        assertVehicleIdNotExists(vehicleId);

        VehiclePhotoEntity vehiclePhotoEntity = new VehiclePhotoEntity();
        vehiclePhotoEntity.setVehicleId(vehicleId);
        vehiclePhotoEntity.setImage(image);
        vehiclePhotoRepository.save(vehiclePhotoEntity);

        return true;
    }

    @Override
    public Boolean deleteVehiclePhoto(Long vehicleId) throws AppException {
        if (vehiclePhotoRepository.existsByVehicleId(vehicleId)) {
            vehiclePhotoRepository.deleteByVehicleId(vehicleId);
        }
        return true;
    }

    @Override
    public Boolean vehicleEntry(Long vehicleId) throws AppException {
        assertVehicleIdNotExists(vehicleId);

        if (!vehicleEntryRecordRepository.existsByVehicleIdAndLeaveTimeIsNull(vehicleId)) {
            VehicleEntryRecordEntity recordEntity = new VehicleEntryRecordEntity();
            recordEntity.setVehicleId(vehicleId);
            recordEntity.setEntryTime(new Timestamp(new Date().getTime()));
            vehicleEntryRecordRepository.save(recordEntity);

            VehicleEntity vehicleEntity = vehicleRepository.getById(vehicleId);
            vehicleEntity.setEntryStatus(1);
            vehicleRepository.save(vehicleEntity);
        }
        return true;
    }

    @Override
    public Boolean vehicleLeave(Long vehicleId) throws AppException {
        assertVehicleIdNotExists(vehicleId);

        if (vehicleEntryRecordRepository.existsByVehicleIdAndLeaveTimeIsNull(vehicleId)) {
            VehicleEntryRecordEntity recordEntity = vehicleEntryRecordRepository.getByVehicleIdAndLeaveTimeIsNull(vehicleId);
            recordEntity.setLeaveTime(new Timestamp(new Date().getTime()));
            vehicleEntryRecordRepository.save(recordEntity);

            VehicleEntity vehicleEntity = vehicleRepository.getById(vehicleId);
            vehicleEntity.setEntryStatus(0);
            vehicleRepository.save(vehicleEntity);
        }
        return true;
    }

    @Override
    public Page<VehicleEntryRecordView> getVehicleEntryRecords(SearchCriteria criteria, Pageable pageable) {
        return vehicleEntryRecordViewRepository.search(criteria, pageable);
    }

    @Override
    public VehicleMoveEntity recordVehicleMove(Long vehicleId, MsgLocate msgLocate) {
        VehicleMoveEntity vehicleMoveEntity = new VehicleMoveEntity();
        vehicleMoveEntity.setVehicleId(vehicleId);
        vehicleMoveEntity.setCardNumber(msgLocate.getCardNumber());
        vehicleMoveEntity.setFrontLocatorAddress(msgLocate.getFrontLocator());
        vehicleMoveEntity.setFrontX(msgLocate.getFrontPos() != null ? msgLocate.getFrontPos().getX() : null);
        vehicleMoveEntity.setFrontY(msgLocate.getFrontPos() != null ? msgLocate.getFrontPos().getY() : null);
        vehicleMoveEntity.setFrontFloorNumber(msgLocate.getFrontFloorNumber());
        vehicleMoveEntity.setCurrentLocatorAddress(msgLocate.getCurrentLocator());
        vehicleMoveEntity.setCurrentX(msgLocate.getCurrentPos() != null ? msgLocate.getCurrentPos().getX() : null);
        vehicleMoveEntity.setCurrentY(msgLocate.getCurrentPos() != null ? msgLocate.getCurrentPos().getY() : null);
        vehicleMoveEntity.setCurrentFloorNumber(msgLocate.getCurrentFloorNumber());
        vehicleMoveEntity.setRecordTime(new Timestamp(msgLocate.getTime()));
        return vehicleMoveRepository.save(vehicleMoveEntity);
    }

    @Override
    public List<LocatorAssetCount> getVehicleDistribution(Integer floorNumber) {
        String sql =
                "select a.locator_address as locatorAddress, a.pos, a.floor_number as floorNumber, count(*) as count " +
                        " from vehicle c " +
                        " left join v_card_active a on c.card_number = a.card_number " +
                        " where c.is_enabled = 1 and a.floor_number = :fn " +
                        " group by a.pos, a.locator_address";
        List<LocatorAssetCount> list = entityManager.createNativeQuery(sql)
                .setParameter("fn", floorNumber)
                .unwrap(NativeQuery.class)
                .addScalar("locatorAddress", IntegerType.INSTANCE)
                .addScalar("pos", new JTSGeometryType(MySQLGeometryTypeDescriptor.INSTANCE))
                .addScalar("floorNumber", IntegerType.INSTANCE)
                .addScalar("count", IntegerType.INSTANCE)
                .setResultTransformer(Transformers.aliasToBean(LocatorAssetCount.class))
                .list();
        return list;
    }

    @Override
    public List<VehicleEntity> getVehicleUnderLocator(Integer locatorNumber) {
        String sql = "select c.* from vehicle c " +
                " left join d_card_active a on c.card_number = a.card_number " +
                " where c.is_enabled = 1 and a.locator_address = :locatorNumber";
        List<VehicleEntity> list = entityManager.createNativeQuery(sql, VehicleEntity.class)
                .setParameter("locatorNumber", locatorNumber)
                .getResultList();
        return list;
    }

    @Override
    public List<VehicleEntity> getVehicleUnderPos(double x, double y) {
        String sql = "select c.* from vehicle c " +
                " left join d_card_active a on c.card_number = a.card_number " +
                " where c.is_enabled = 1 and st_contains(:pg, point(a.x, a.y))";
        List<VehicleEntity> list = entityManager.createNativeQuery(sql, VehicleEntity.class)
                .setParameter("pg", GeomUtil.INSTANCE.createSmallPolygon(x, y))
                .getResultList();
        return list;
    }

    private List<VehicleMoveDto> getVehicleMoveList(Long vehicleId, Timestamp startTime, Timestamp endTime) {
        String sql = "select m.id, m.vehicle_id, m.card_number, m.front_locator_address, m.current_locator_address, " +
                " m.record_time, p.id as location_id, p.location_name, p.location_type, p.map_level, p.location_centroid, " +
                " p.floor_number, p.bind_org_id " +
                " from vehicle_move m left join d_locator d on m.current_locator_address = d.locator_address " +
                " left join (select * from poi_location where map_level = 3) p " +
                " on st_contains(p.location_polygon, d.pos) and p.floor_number = d.floor_number " +
                " where m.vehicle_id = :vid " +
                " and (m.record_time BETWEEN :start and :stop) " +
                " order by m.id";
        List<VehicleMoveDto> moveList = entityManager.createNativeQuery(sql, VehicleMoveDto.class)
                .setParameter("vid", vehicleId)
                .setParameter("start", startTime)
                .setParameter("stop", endTime)
                .getResultList();
        return moveList;
    }

    private VehicleMoveInLocationDto createMoveInLocationDto(VehicleMoveDto moveDto) {
        VehicleMoveInLocationDto moveInLocationDto = new VehicleMoveInLocationDto();
        moveInLocationDto.setVehicleId(moveDto.getVehicleId());
        moveInLocationDto.setCardNumber(moveDto.getCardNumber());
        moveInLocationDto.setLocationId(moveDto.getLocationId());
        moveInLocationDto.setLocationName(moveDto.getLocationName());
        moveInLocationDto.setLocationType(moveDto.getLocationType());
        moveInLocationDto.setLocationCentroid(moveDto.getLocationCentroid());
        moveInLocationDto.setFloorNumber(moveDto.getFloorNumber());
        moveInLocationDto.setEnterTime(moveDto.getRecordTime());
        return moveInLocationDto;
    }

    @Override
    public List<VehicleMoveInLocationDto> getVehicleMoveLocationList(Long vehicleId, Timestamp startTime, Timestamp endTime) {
        List<VehicleMoveDto> moveList = getVehicleMoveList(vehicleId, startTime, endTime);
        if (moveList.size() > 0) {
            List<VehicleMoveInLocationDto> list = new ArrayList<>();
            int pos = 0;
            for (int i = 0; i < moveList.size(); i++) {
                VehicleMoveDto moveDto = moveList.get(i);
                if (moveDto.getLocationId() != null) {
                    list.add(createMoveInLocationDto(moveDto));
                    pos = i;
                    break;
                }
            }
            for (int i = pos + 1; i < moveList.size(); i++) {
                VehicleMoveInLocationDto dto = list.get(list.size() - 1);
                VehicleMoveDto frontDto = moveList.get(i - 1);
                VehicleMoveDto currentDto = moveList.get(i);
                if (frontDto.getLocationId() != null) {
                    dto.setLeaveTime(currentDto.getRecordTime());
                }
                if ((currentDto.getLocationId() != null && !currentDto.getLocationId().equals(dto.getLocationId()))
                        || (currentDto.getLocationId() != null && frontDto.getLocationId() == null)) {
                    list.add(createMoveInLocationDto(currentDto));
                }
            }
            return list;
        } else {
            return new ArrayList<>();
        }
    }

    @Override
    public List<VehicleInfoDto> getVehicleByPlateNumber(String plateNumber){

        String sql =
                "select  c.id as id,c.card_number as card_number,c.vehicle_sn as vehicle_sn,c.plate_number as plate_number,c.driver_name as driver_name,c.vehicle_use as vehicle_use," +
                        "c.vehicle_model as vehicle_model,c.is_enabled as is_enabled,c.is_outside as is_outside,c.company as company,c.visit_reason as visit_reason" +
                        ",c.linkman as linkman,c.entry_status as entry_status, a.pos as pos "+
                        " from vehicle c " +
                        " left join v_card_active a on c.card_number = a.card_number " +
                        " where c.is_enabled = 1 and c.plate_number like '%"+ plateNumber +"%'";
        List<VehicleInfoDto> list =  entityManager.createNativeQuery(sql, VehicleInfoDto.class)
                .getResultList();
        return list;
    }


    @Override
    public List<VehicleInfoDto> getVehicleInPolygon(Integer floorNumber, Polygon polygon) {
        if (floorNumber != null && polygon != null) {
            try{
                String sql =
                        "select  c.id as id,c.card_number as card_number,c.vehicle_sn as vehicle_sn,c.plate_number as plate_number,c.driver_name as driver_name,c.vehicle_use as vehicle_use," +
                                "c.vehicle_model as vehicle_model,c.is_enabled as is_enabled,c.is_outside as is_outside,c.company as company,c.visit_reason as visit_reason" +
                                ",c.linkman as linkman,c.entry_status as entry_status, a.pos as pos  "+
                                " from vehicle c " +
                                " left join v_card_active a on c.card_number = a.card_number  " +
                                " where c.is_enabled = 1 and a.floor_number = :fn and  st_contains(:pg, a.pos)";
                List<VehicleInfoDto> list = entityManager.createNativeQuery(sql, VehicleInfoDto.class)
                        .setParameter("fn", floorNumber)
                        .setParameter("pg", polygon)
                        .getResultList();
                return list;
            }catch (Exception e){
                e.printStackTrace();
                return  new ArrayList<>();
            }

        } else {
            return new ArrayList<>();
        }
    }
}
